﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;

namespace Dycox.Linq
{
    public abstract class ConditionEvaluator
    {
        protected const char BRACKET_OPEN = '(', BRACKET_CLOSE = ')';

        LambdaExpression _expression;
        string _condition;

        public string Condition
        {
            get { return _condition; }
            set
            {
                if (_condition != value)
                {
                    _condition = value;
                    _expression = null;
                }
            }
        }

        public ConditionEvaluator()
        {

        }

        public ConditionEvaluator(string condition)
            : this()
        {
            Condition = condition;
        }

        public override string ToString()
        {
            if (_expression != null)
                return _expression.ToString();
            else
                return string.Empty;
        }

        public LambdaExpression GetExpression()
        {
            if (_expression == null)
            {
                if (string.IsNullOrWhiteSpace(_condition))
                    throw new InvalidOperationException("Condition not set or is empty.");

                _expression = CreateExpression(_condition);
            }

            return _expression;
        }

        public bool Execute(object param)
        {
            if (param == null)
                throw new ArgumentNullException("param");

            if (!ParameterType.IsAssignableFrom(param.GetType()))
                throw new ArgumentException("Type not match.");

            return (bool)GetExpression().Compile().DynamicInvoke(param);
        }

        private LambdaExpression CreateExpression(string text)
        {
            Queue<Token> queue = new Queue<Token>();
            ParseTokens(text, queue);
            queue.Enqueue(new BracketToken(true));

            ParameterExpression param = Expression.Parameter(ParameterType, "p");
            Expression expr = ParseBrackets(queue, param);

            return Expression.Lambda(expr, param);
        }

        protected abstract Type ParameterType
        {
            get;
        }

        private Expression ParseBrackets(Queue<Token> queue, ParameterExpression param)
        {
            Stack<Token> stack = new Stack<Token>(queue.Count);

            while (queue.Count > 0)
            {
                Token token = queue.Dequeue();
                bool tryMerge = false;

                switch (token.Type)
                {
                    case TokenType.Literal:
                        stack.Push(token);
                        tryMerge = true;
                        break;
                    case TokenType.BinaryOperator:
                        if (stack.Count == 0)
                            throw new SyntaxException("Excepted for expression.");
                        stack.Push(token);
                        break;
                    case TokenType.UnaryOperator:
                        stack.Push(token);
                        break;
                    case TokenType.OpenBracket:
                        stack.Push(new RawExpressionToken(ParseBrackets(queue, param)));
                        tryMerge = true;
                        break;
                    case TokenType.CloseBracket:
                        {
                            if (stack.Count > 0)
                            {
                                ExpressionToken raw = stack.Pop() as ExpressionToken;
                                if (raw == null)
                                    throw new SyntaxException();

                                return raw.CreateExpression(param);
                            }
                            else
                                return null;
                        }
                    default:
                        throw new SyntaxException();
                }

                if (tryMerge && stack.Count > 1)
                {
                    Token right = stack.Pop(), op = null;
                    Expression x = ((ExpressionToken)right).CreateExpression(param);

                    while (stack.Count > 0)
                    {

                        op = stack.Pop();
                        if (!(op is OperatorToken))
                            throw new SyntaxException("Expected for operator.");

                        if (op.Type == TokenType.UnaryOperator)
                        {
                            x = Expression.MakeUnary(((OperatorToken)op).ExpressionType, x, null);
                        }
                        else if (op.Type == TokenType.BinaryOperator)
                        {
                            Token left = null;

                            if (stack.Count > 0)
                                left = stack.Pop();

                            if (left == null || left.Type != TokenType.Literal)
                                throw new SyntaxException("Expected for oprand.");

                            x = Expression.MakeBinary(((OperatorToken)op).ExpressionType, ((ExpressionToken)left).CreateExpression(param), x);

                        }
                        else
                            throw new SyntaxException("Expected for operator.");
                    }

                    if (stack.Count > 0)
                        throw new SyntaxException();

                    stack.Push(new RawExpressionToken(x));
                }

            }

            throw new SyntaxException("Expected for close bracket.");
        }

        private void ParseTokens(string text, Queue<Token> queue)
        {
            ParseConditionState state = new ParseConditionState(text);

            state.SkipWhiteSpaces();
            while (!state.EOT)
            {
                char ch = state.CurrentChar;
                Token token;

                if (ch == BRACKET_OPEN)
                {
                    token = new BracketToken(false);
                }
                else if (ch == BRACKET_CLOSE)
                {
                    token = new BracketToken(true);
                }
                else
                {
                    StringBuilder sb = new StringBuilder();
                    CharCategory? lc = null;

                    while (!state.EOT)
                    {
                        ch = state.CurrentChar;
                        if (char.IsWhiteSpace(ch))
                        {
                            state.SkipWhiteSpaces();
                            state.MovePrevious();
                            break;
                        }
                        else if (ch == BRACKET_OPEN || ch == BRACKET_CLOSE)
                        {
                            state.MovePrevious();
                            break;
                        }
                        else
                        {
                            CharCategory cc = GetCategory(ch);
                            if (lc == null) lc = cc;
                            if (lc.Value == cc)
                            {
                                sb.Append(ch);

                                if (cc == CharCategory.Symbol)
                                {
                                    if (sb.Length > 1 && !OperatorToken.IsOperatorSupported(sb.ToString()))
                                    {
                                        state.MovePrevious();
                                        sb.Remove(sb.Length - 1, 1);
                                        break;
                                    }
                                }

                                state.MoveNext();
                            }
                            else
                            {
                                state.MovePrevious();
                                break;
                            }
                        }
                    }

                    if (sb.Length > 0)
                        token = CreateToken(sb.ToString(), lc.Value);
                    else
                        token = null;
                }

                if (token != null)
                    queue.Enqueue(token);

                state.MoveNext();
            }

        }

        private Token CreateToken(string text, CharCategory cc)
        {
            if (cc == CharCategory.Symbol)
                return OperatorToken.CreateToken(text);
            else
                return CreateLiteralToken(text);
        }

        protected abstract LiteralToken CreateLiteralToken(string text);

        static CharCategory GetCategory(char ch)
        {
            if (char.IsLetterOrDigit(ch) || ch == '.' || ch == '_') return CharCategory.Letter;
            else return CharCategory.Symbol;
        }

        enum CharCategory
        {
            Letter,
            Symbol
        }

        protected class ParseConditionState
        {
            public ParseConditionState(string text)
            {
                if (text == null)
                    throw new ArgumentNullException("text");

                _text = text;
                _textLength = text.Length;

                Reset();
            }

            private string _text;

            public string Text
            {
                get { return _text; }
            }

            private int _textLength;

            public int TextLength
            {
                get { return _textLength; }
            }

            private int _position;

            public int Position
            {
                get { return _position; }
            }

            public char CurrentChar
            {
                get
                {
                    if (_position < _textLength)
                        return _text[_position];
                    else
                        throw new InvalidOperationException();
                }
            }

            public bool MoveNext()
            {
                if (_position < _textLength)
                {
                    _position++;
                    return _position < _textLength;
                }
                else
                    return false;
            }

            public void MovePrevious()
            {
                if (_position > 0)
                    _position--;
                else
                    throw new InvalidOperationException();
            }

            public void Reset()
            {
                _position = 0;
            }

            public void SkipWhiteSpaces()
            {
                while (!EOT && char.IsWhiteSpace(CurrentChar))
                {
                    MoveNext();
                }
            }

            public bool EOT
            {
                get
                {
                    return _position >= _textLength;
                }
            }
        }
    }

    [Serializable]
    public class SyntaxException : Exception
    {
        public SyntaxException() : this("Syntax error.") { }
        public SyntaxException(string message) : base(message) { }
        public SyntaxException(string message, Exception inner) : base(message, inner) { }
        protected SyntaxException(
          System.Runtime.Serialization.SerializationInfo info,
          System.Runtime.Serialization.StreamingContext context)
            : base(info, context) { }
    }
}
